package mt.server;

import java.util.LinkedList;
import java.util.Random;

import mt.Order;
import mt.comm.ServerComm;
import mt.comm.ServerSideMessage;
import mt.comm.ServerSideMessage.Type;
import mt.dummy.ServerCommDummy;
import mt.server.MicroTraderServer;

/**
 * @author Grupo 48
 * @author Szabolcs Gnandt
 * @author Davide Ribeiro
 * @author Filipe Pereira
 * 
 */

public class MicroTraderServerDummy implements MicroTraderServer {
	/**
	 * A list with all the clients currently online.
	 */
	private LinkedList<String> clientsOnline = new LinkedList<String>();
	
	/**
	 * A list with all the unsatisfied selling orders.
	 */
	private LinkedList<Order> sellingOrders = new LinkedList<Order>();
	
	/**
	 * A list with all the unsatisfied buying orders.
	 */
	private LinkedList<Order> buyingOrders = new LinkedList<Order>();
	
	/**
	 * Object that assures the server side communication
	 */
	private ServerCommDummy serverComm;
	
	/**
	 * Variable that keeps the new number of units of a partially satisfied order
	 */
	private int orderNewNrUnits;

	/**
	 * Starts the server invoked by serverComm and keeps waiting for new messages
	 * 
	 * @param serverComm Interface responsible for the server side communication
	 * 
	 */

	@Override
	public void start(ServerComm serverComm) {
		this.serverComm = (ServerCommDummy) serverComm;
		serverComm.start();
		while (true) {
			if (serverComm.hasNextMessage()) {
				ServerSideMessage message = (ServerSideMessage) serverComm
						.getNextMessage();
				System.out.println(message.getSenderNickname() + " "
						+ message.getType());
				sortMessage(message);
			}
		}
	}

	/**
	 * 
	 * Method that handles the messages according to their type. They might be
	 * new purchase/sale orders or connection/disconnection requests to the
	 * server.
	 * 
	 * @param message Message sent by the client to the server
	 */

	public void sortMessage(ServerSideMessage message) {
		Type type = message.getType();
		String username = message.getSenderNickname();
		Order order = message.getOrder();

		if (type.equals(Type.CONNECTED)) {
			treatClient(username);
		} else if (type.equals(Type.DISCONNECTED)) {
			clientsOnline.remove(username);
			updateOrdersFromDisconnectedClients(username);
		} else if (type.equals(Type.NEW_ORDER)) {
			sortOrder(order);
		}
	}

	/**
	 * Method that takes a buy or sell order and sorts it. It calls other two methods according to the
	 * type of the order.
	 * 
	 * @param order Order sent by the client to the server.
	 */

	private synchronized void sortOrder(Order order) {
		Random id = new Random();
		int serverOrderID = id.nextInt(10000);
		order.setServerOrderID(serverOrderID);

		if (order.isBuyOrder()) {
			buyingOrders.add(order);
			treatBuyOrder(order);

		} else {
			sellingOrders.add(order);
			treatSellOrder(order);
		}
	}

	/**
	 * Updates the clients and the unsatisfied lists if applicable.
	 * @param order Sell order
	 */
	
	private void treatSellOrder(Order order) {
		updateClients(order);

		String stock = order.getStock();
		int nrUnits = order.getNumberOfUnits();
		double pricePerUnit = order.getPricePerUnit();

		for (Order entry : buyingOrders) {
			if (entry.getStock().equals(stock)
					&& entry.getPricePerUnit() >= pricePerUnit) {
				if (entry.getNumberOfUnits() == nrUnits) {
					sellingOrders.remove(order);
					buyingOrders.remove(entry);

				} else if (entry.getNumberOfUnits() > nrUnits) {
					orderNewNrUnits = entry.getNumberOfUnits() - nrUnits;
					entry.setNumberOfUnits(orderNewNrUnits);
					sellingOrders.remove(order);

				} else {
					orderNewNrUnits = order.getNumberOfUnits()
							- entry.getNumberOfUnits();
					buyingOrders.remove(entry);
					for (Order sell_entry : sellingOrders) {
						if (sell_entry.equals(order)) {
							sell_entry.setNumberOfUnits(orderNewNrUnits);
						}
					}
				}
			}
		}

	}
	
	/**
	 * Updates the clients and the unsatisfied lists if applicable.
	 * @param order Buy order
	 */

	private void treatBuyOrder(Order order) {
		updateClients(order);
		String stock = order.getStock();
		int nrUnits = order.getNumberOfUnits();
		double pricePerUnit = order.getPricePerUnit();

		for (Order entry : sellingOrders) {
			if (entry.getStock().equals(stock)
					&& entry.getPricePerUnit() <= pricePerUnit) {
				if (entry.getNumberOfUnits() == nrUnits) {
					sellingOrders.remove(order);
					buyingOrders.remove(entry);
				} else if (entry.getNumberOfUnits() > nrUnits) {
					orderNewNrUnits = entry.getNumberOfUnits() - nrUnits;
					entry.setNumberOfUnits(orderNewNrUnits);
					buyingOrders.remove(order);
				} else {
					orderNewNrUnits = order.getNumberOfUnits()
							- entry.getNumberOfUnits();
					sellingOrders.remove(entry);
					for (Order buy_entry : buyingOrders) {
						if (buy_entry.equals(order)) {
							buy_entry.setNumberOfUnits(orderNewNrUnits);
						}
					}
				}
			}
		}

	}

	/**
	 * Checks the online clients list to see if the username is currently in use. If not,
	 * it adds the client to the online list and updates the client. If yes, it sends an error.
	 * @param nickname Nickname of the client that just connected to the server
	 */

	private void treatClient(String nickname) {
		System.out.println("A tratar do cliente: " + nickname);

		if (clientsOnline.isEmpty()) {
			clientsOnline.add(nickname);
			updateClients(nickname);
		}

		else {
			Boolean username_in_use = isClientAlready(nickname);
			if (username_in_use == true) {
				serverComm.sendError(nickname,
						"Can't connect: Nickname is being used right now");
			} else {
				clientsOnline.add(nickname);
				updateClients(nickname);
			}
		}
	}
	
	/**
	 * Checks if client's nickname is already being used at the moment.
	 * @param nickname Nickname to check it it's being used at the moment.
	 * @return true if nickname is already online; false otherwise.
	 */

	private boolean isClientAlready(String nickname) {
		for (int i = 0; i < clientsOnline.size(); i++) {
			if (clientsOnline.get(i).trim().equals(nickname)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Method that allows a particular customer to be updated of all the changes
	 * in the purchasing and selling lists.
	 * 
	 * @param nickname Nickname of the client to be updated.
	 */

	private void updateClients(String nickname) {
		if (!sellingOrders.isEmpty()) {
			for (Order entry : sellingOrders) {
				if (entry.getNickname() != nickname) {
					serverComm.sendOrder(nickname, entry);
				}
			}
		}
		if (!buyingOrders.isEmpty()) {
			for (Order entry : buyingOrders) {
				if (entry.getNickname() != nickname) {
					serverComm.sendOrder(nickname, entry);
				}
			}
		}
		return;
	}

	/**
	 * Method that sends a specific update to all the clients online.
	 * 
	 * @param order Order to send to the clients
	 */

	private void updateClients(Order order) {
		for (int i = 0; i < clientsOnline.size(); i++) {
			serverComm.sendOrder(clientsOnline.get(i), order);
		}
	}

	/**
	 * Method that removes all the orders of a user that disconnected from the
	 * server.
	 * 
	 * @param username
	 */

	private void updateOrdersFromDisconnectedClients(String username) {
		for (Order c : sellingOrders) {
			if (c.getNickname().equals(username)) {
				updateClients(c);
				sellingOrders.remove(c);
			}
		}

		for (Order d : buyingOrders) {
			if (d.getNickname().equals(username)) {
				d.setNumberOfUnits(0);
				updateClients(d);
				buyingOrders.remove(d);
			}
		}

		for (String clients : clientsOnline) {
			updateClients(clients);
		}
	}
}
